package client;
//
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.TreeMap;

import view.JanelaDeConversa;
import view.JanelaDoCliente;

import log.GeradorDeLog;
import model.*;

public class Cliente {

	public static final boolean ONLINE = true;
	public static final boolean OFFLINE = false;
	private Contato usuario;
	private TreeMap<Contato, String> contatosOnline;
	private TreeMap<String, Contato> contatosOnlineReverso;
	// mapea um ip numa janela de conversa, que tem uma conversa
	private TreeMap<String, JanelaDeConversa> conversas;
	private boolean estado;
	private ServerSocket socketDoServidor;
	private Socket conexaoDoServidor;
	private ObjectInputStream fluxoDoServidor;
	private Socket socketParaServidor;
	private ObjectOutputStream fluxoParaServidor;
	private Thread threadRecebeMensagens;
	private GeradorDeLog log;
	private JanelaDoCliente janela;

	// criado o construtor vazio
	public Cliente(JanelaDoCliente janela) {
		// TODO Auto-generated constructor stub
		log = new GeradorDeLog();
		log.escreve("Cliente Inicado");
		estado = Cliente.OFFLINE;
		this.janela = janela;
		contatosOnline = new TreeMap<Contato, String>();
		contatosOnlineReverso = new TreeMap<String, Contato>();
		conversas = new TreeMap<String, JanelaDeConversa>();
		// instancia a thread que irá rodar o metodo recebeDadosServidor
		threadRecebeMensagens = new Thread(new Runnable() {

			@Override
			public void run() {
				// TODO Auto-generated method stub
				try {
					recebeMensagens();
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				} catch (ClassNotFoundException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		});
		// inicia a thread
		threadRecebeMensagens.start();
	}

	private void recebeMensagens() throws IOException, ClassNotFoundException {
		log.escreve("Aguardando recebimento de mensagens");
		// o método sera chamado uma vez e "separará" as tarefas a serem
		// executadas sempre que chegar uma mensagem de controle
		socketDoServidor = new ServerSocket(Configuracao.PORTA_COMUNICACAO);
		while ((conexaoDoServidor = socketDoServidor.accept()) != null) {
			// instancia gerador de log
			log = new GeradorDeLog();
			log.escreve("Mensagem recebida");
			fluxoDoServidor = new ObjectInputStream(conexaoDoServidor
					.getInputStream());
			// trata o cast, pois o cliente pode receber qualquer coisa que não
			// seja mensagem de conrole
			try {
				Enviavel enviavel = (Enviavel) fluxoDoServidor.readObject();
				trataEntrada(enviavel);
			} catch (ClassCastException e) {
				log.escreve("Mensagem rejeitada");
			}

			fluxoDoServidor.close();
			conexaoDoServidor.close();
			log.fechaLog();
		}

	}

	private void enviaParaServidor(MensagemDeAutenticacao mensagemDeAutenticacao)
			throws IOException, CloneNotSupportedException {
		this.socketParaServidor = new Socket(Configuracao.SERVIDOR_IP,
				Configuracao.PORTA_COMUNICACAO);
		fluxoParaServidor = new ObjectOutputStream(socketParaServidor
				.getOutputStream());
		log.escreve("Conexao estabelecida com o servidor");
		// manda o objeto para o servidor
		fluxoParaServidor.writeObject(mensagemDeAutenticacao);
	}

	private void trataEntrada(Enviavel enviavel) {
		if (enviavel instanceof MensagemDeAutenticacao) {
			// Chama o método que trata a entrada de autenticação

			log.escreve("Resposta de autenticacao recebida do servidor");
			try {
				recebeRespostaAutenticacao((MensagemDeAutenticacao) enviavel);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}

		} else if (enviavel instanceof MensagemDeListaDeContatos) {
			// Chama o método que trata a entrada de lista de contatos
			log.escreve("Lista de contatos recebida do servidor");
			atualizaContatos((MensagemDeListaDeContatos) enviavel);
		} else if (enviavel instanceof RecebivelPorConversa) {
			// Chama o método que trata para qual conversa ira a mensagem
			log.escreve("Mensagem de conversa recebida do servidor");
			entregaMensagemParaConversa((RecebivelPorConversa) enviavel);
		}
	}

	private void entregaMensagemParaConversa(
			RecebivelPorConversa recebivelPorConversa) {
		// Pega a janela de conversa referente ao ip do remetente
		JanelaDeConversa janelaDeConversa = conversas.get(recebivelPorConversa
				.getIpRemetente());
		if (janelaDeConversa == null) {
			// Cria nova janela caso não haja entrada para o ip dado
			// Nova janela é criada a partir da conversa criada acima
			janelaDeConversa = new JanelaDeConversa(recebivelPorConversa);
			janelaDeConversa.getConversa().setContatoDestino(contatosOnlineReverso.get(recebivelPorConversa
				.getIpRemetente()));
			// Janela adicionada ao mapa de conversas, com a chave do ip
			conversas.put(recebivelPorConversa.getIpRemetente(),
					janelaDeConversa);
		} else {
			// Direciona a mensagem para a janela criada
			janelaDeConversa.getConversa().trataMensagemRecebida(
					recebivelPorConversa);
		}
	}

	// método criado para facilitar utilização com a GUI
	public void solicitaAutenticacao(String login, String senha)
			throws IOException, ClassNotFoundException,
			CloneNotSupportedException {
		log = new GeradorDeLog();
		log.escreve("Autenticacao solicitada para " + login);
		if (estado == Cliente.OFFLINE) {
			usuario = new Contato(null, login);
			solicitaAutenticacao(senha);
		} else
			log.escreve("Voce nao pode solicitar login, pois ja esta logado");
		log.fechaLog();
	}

	private void solicitaAutenticacao(String senha) throws IOException,
			ClassNotFoundException, CloneNotSupportedException {
		MensagemDeAutenticacao autenticacao = new MensagemDeAutenticacao(
				usuario, senha);
		autenticacao.setTipo(MensagemDeAutenticacao.LOGIN);
		enviaParaServidor(autenticacao);
		log.escreve("Pedido de autenticacao enviado para o servidor");
		// o método recebeDadosServidor está em loop
		// recebeDadosServidor();
	}

	@SuppressWarnings("deprecation")
	public void solicitaLogout() throws IOException, ClassNotFoundException,
			CloneNotSupportedException {
		log = new GeradorDeLog();
		if (estado == Cliente.ONLINE) {
			MensagemDeAutenticacao autenticacao = new MensagemDeAutenticacao(
					usuario, null);
			autenticacao.setTipo(MensagemDeAutenticacao.LOGOUT);
			enviaParaServidor(autenticacao);
			// interrompe o recebimento de resposta do servidor
			threadRecebeMensagens.stop();
			socketDoServidor.close();
			log.escreve("Voce deslogou");
		} else
			log.escreve("Voce nao pode deslogar, pois nao esta logado");
		log.fechaLog();
	}

	private void recebeRespostaAutenticacao(MensagemDeAutenticacao autenticacao)
			throws IOException, ClassNotFoundException {
		// prossegue ou nao de acordo com o aval do servidor
		if (autenticacao.getValidacao()) {
			// autorizado. Passará para a janela onde os contatos sao
			// vizualidados e conversar iniciadas
			// passa a ter o nome do usuario
			usuario = autenticacao.getUsuario();
			// loga o cliente
			estado = Cliente.ONLINE;
			log.escreve("Tentativa de login aprovada pelo servidor, bem vindo "
					+ usuario.getNome());
		} else {
			// nao autorizado. Exibe mensagem de erro
			log.escreve("Tentativa de login rejeitada pelo servidor");
		}
	}

	private void atualizaContatos(
			MensagemDeListaDeContatos mensagemDeListaDeContatos) {
		// verifica se a primeira e ultima chaves sao iguais, ou seja, se só tem
		// uma chave
		if (mensagemDeListaDeContatos.getContatosOnline().firstKey().equals(
				mensagemDeListaDeContatos.getContatosOnline().lastKey())) {
			// cria um novo contato, que é o unitario da lista
			Contato contato = mensagemDeListaDeContatos.getContatosOnline()
					.lastKey();
			// confere se o contato deverá ser adicionado ou removido
			if (mensagemDeListaDeContatos.isTipo() == MensagemDeListaDeContatos.LOGOU) {
				// adiciona este contato à minha lista de contato
				this.contatosOnline.put(contato, mensagemDeListaDeContatos
						.getContatosOnline().get(contato));
				this.contatosOnlineReverso.put(mensagemDeListaDeContatos
						.getContatosOnline().get(contato), contato);
			} else {
				// remove este contato à minha lista de contato
				this.contatosOnline.remove(contato);
				this.contatosOnlineReverso.remove(mensagemDeListaDeContatos
						.getContatosOnline().get(contato));
			}
		} else {
			// troca toda lista
			this.contatosOnline = mensagemDeListaDeContatos.getContatosOnline();
			String[] ips = (String[]) contatosOnline.values().toArray();
			Contato[] conts = (Contato[]) contatosOnline.keySet().toArray();
			for (int i = 0; i < ips.length; i++) {
				this.contatosOnlineReverso.put(ips[i], conts[i]);
			}

		}
			
			// passa para a janela o Set de contatos on-line
			janela.atualizaContatos();

	}

	public boolean isEstado() {
		return estado;
	}

	public Contato getUsuario() {
		return usuario;
	}

	public TreeMap<Contato, String> getContatosOnline() {
		return contatosOnline;
	}
	
	public TreeMap<String, JanelaDeConversa> getConversas(){
		return conversas;
	}
	
	public void addConversa(String ip, JanelaDeConversa janela){
		conversas.put(ip, janela);
	}
	
	
	

}
